﻿namespace My.Hydrator.Configuration
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using My.Hydrator.Conventions;
    using My.Hydrator.Helpers;

    public class PropertyExpression<TClass, TResult>
    {
        private readonly Expression<Func<TClass, TResult>> propertyExpression;

        public PropertyExpression(Expression<Func<TClass, TResult>> property)
        {
            this.propertyExpression = property;
        }

        protected Container Container
        {
            get { return SessionHelper.Container; }
        }

        public void Use<TConvention>() where TConvention : ITypeConvention, new()
        {
            var convention = new TConvention();

            var type = typeof(TResult);
            if (convention.Type != type && !type.IsNullable(convention.Type))
            {
                throw new ArgumentException();
            }

            var className = typeof(TClass).FullName;
            var propertyName = ReflectionHelper.GetFullPropertyName(this.propertyExpression);

            IDictionary<string, ITypeConvention> propertyConventions;

            if (this.Container.CustomConventions.TryGetValue(className, out propertyConventions))
            {
                if (propertyConventions.ContainsKey(propertyName))
                {
                    propertyConventions[propertyName] = convention;
                }
                else
                {
                    propertyConventions.Add(propertyName, convention);
                }
            }
            else
            {
                propertyConventions = new Dictionary<string, ITypeConvention>
                                              {
                                                  { propertyName, convention },
                                              };

                this.Container.CustomConventions.Add(className, propertyConventions);
            }
        }

        public void Use(ITypeConvention typeConvention)
        {
            var type = typeof(TResult);

            if (typeConvention.Type != type && !type.IsNullable(typeConvention.Type))
            {
                if (type.IsDictionary())
                {
                    var genericArguments = type.GetGenericArguments();
                    if (typeConvention.Type != typeof(Dictionary<,>).MakeGenericType(new[] { genericArguments[0], genericArguments[1] }))
                    {
                        throw new ArgumentException();
                    }
                }
                else if (type.IsList())
                {
                    var genericArguments = type.GetGenericArguments();
                    if (typeConvention.Type != typeof(List<>).MakeGenericType(new[] { genericArguments[0] }))
                    {
                        throw new ArgumentException();
                    }
                }
                else
                {
                    throw new ArgumentException();
                }
            }

            var className = typeof(TClass).FullName;
            var propertyName = ReflectionHelper.GetFullPropertyName(this.propertyExpression);

            this.AddOrReplaceCustomConvention(className, propertyName, typeConvention);
        }

        public void Is(CommonType commonType)
        {
            var type = typeof(TResult);

            if (type != typeof(string) && type != typeof(int))
            {
                if (type.IsNullable<string>() || type.IsNullable<int>())
                {
                    var genericArguments = type.GetGenericArguments();
                    var nullableType = genericArguments.FirstOrDefault();
                    if (nullableType != null)
                    {
                        if (nullableType != typeof(string) && nullableType != typeof(int))
                        {
                            throw new NotSupportedException();
                        }
                    }
                    else
                    {
                        throw new NotSupportedException();
                    }
                }
                else
                {
                    throw new NotSupportedException();
                }
            }

            ITypeConvention typeConvention = null;

            if (type == typeof(string) || type.IsNullable<string>())
            {
                typeConvention = new CommonTypeConvention<string>(commonType);
            }
            else if (type == typeof(int) || type.IsNullable<int>())
            {
                typeConvention = new CommonTypeConvention<int>(commonType);
            }

            var className = typeof(TClass).FullName;
            var propertyName = ReflectionHelper.GetFullPropertyName(this.propertyExpression);

            this.AddOrReplaceCustomConvention(className, propertyName, typeConvention);
        }

        public void Skip()
        {
            var className = typeof(TClass).FullName;
            var propertyName = ReflectionHelper.GetFullPropertyName(this.propertyExpression);
            var convention = new DefaultTypeConvention<TResult>();

            this.AddOrReplaceCustomConvention(className, propertyName, convention);
        }

        private void AddOrReplaceCustomConvention(string className, string propertyName, ITypeConvention typeConvention)
        {
            IDictionary<string, ITypeConvention> propertyConventions;

            if (this.Container.CustomConventions.TryGetValue(className, out propertyConventions))
            {
                if (propertyConventions.ContainsKey(propertyName))
                {
                    propertyConventions[propertyName] = typeConvention;
                }
                else
                {
                    propertyConventions.Add(propertyName, typeConvention);
                }
            }
            else
            {
                propertyConventions = new Dictionary<string, ITypeConvention>
                                              {
                                                  { propertyName, typeConvention },
                                              };

                this.Container.CustomConventions.Add(className, propertyConventions);
            }
        }
    }
}
